{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# JavaScript 中的函数\n",
    "\n",
    "函数对于任何一门语言来说，都是核心概念。通过函数，可以封装任意多条语句，\n",
    "\n",
    "## 函数的基本用法\n",
    "\n",
    "JavaScript可使用 `function` 关键字来声明，后面跟一组参数以及函数体。基本语法如下：\n",
    "\n",
    "```js\n",
    "function funname(arg0, arg1, , ,argN) {\n",
    "    statements\n",
    "}\n",
    "```\n",
    "\n",
    "以下是一个实例："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你好js, I love js\n"
     ]
    }
   ],
   "source": [
    "function sayHello(name, message){\n",
    "    console.log('你好' + name + ', ' + message )\n",
    "}\n",
    "\n",
    "sayHello(\"js\",'I love js')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在定义函数名和函数体的时候使用的参数，叫做**形式参数**，如上例中的`name`和`message`，目的是用来接收调用该函数时传递的参数。而调用函数时实际给出的值，可以是常量、变量、表达式、函数等，叫做**实际参数**，比如上例中的`'js'`和`'I love js'`。\n",
    "\n",
    "\n",
    "在JavaScript中，函数可以重新定义。如果定义了两个名字相同的函数，最后定义的函数有效。例如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hi, js, I love js\n"
     ]
    }
   ],
   "source": [
    "function sayHello(name, message){\n",
    "    console.log('Hi, ' + name + ', ' + message )\n",
    "}\n",
    "\n",
    "sayHello(\"js\",'I love js')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 返回值\n",
    "\n",
    "在JavaScript中定义函数时，不必指定返回值。如有需要，可以在需要的地方，通过`return`语句实现返回值。`return`语句之后的代码不会执行。例如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "6"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "function sum(num1, num2){\n",
    "    return num1 + num2\n",
    "    console.log('这句命令不会执行')\n",
    "}\n",
    "\n",
    "sum(2, 4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果没有指定返回值，函数将在执行完毕后返回`undefined`值。推荐的做法是，要么让函数始终都返回一个值，要么永远不要返回值，以便后期调试和维护。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 参数\n",
    "\n",
    "### 参数数量\n",
    "\n",
    "JavaScript 函数不介意传递进来多少个参数，也不在乎参数是什么数据类型，即使你定义了参数的类型和数量。以之前定义的`sum()`函数为例："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "NaN"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sum(1, 2, '字符串')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'字符串3'"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sum('字符串',3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "之所以会这样，是因为在JavaScript中，参数是用一个数组`arguments`来表示的。函数接收到的始终是这个数组，在函数体内部，可以通过`arguments`对象来访问这个参数数组，从而获得所有的参数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "9"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "function add(a, b) {\n",
    "    let score = 0\n",
    "    for (var i in arguments) {\n",
    "            score += arguments[i]\n",
    "        }\n",
    "        return score\n",
    "}\n",
    "\n",
    "add(9)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "55"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add(1,2,3,4,5,6,7,8,9,10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "add(0).arguments"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "函数的参数不能相同，否则无法使用命名参数。如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Arguments] { '0': 1, '1': 2 }\n",
      "undefined undefined undefined\n"
     ]
    }
   ],
   "source": [
    "function func2(a,a,a) {\n",
    "    console.log(arguments)\n",
    "    console.log(a,a,a)\n",
    "}\n",
    "\n",
    "func2(1,2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "没有传递值的命名参数，如果没有默认值的话，则会被赋予`undefined`值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[ 3, 0, undefined ]"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "function func3(arg1, arg2=0, arg3) {\n",
    "    return [arg1,arg2,arg3]\n",
    "}\n",
    "\n",
    "func3(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 传递参数\n",
    "\n",
    "JavaScript 中所有函数的参数都是按值传递的，即便是引用类型的变量（如对象），也是按值传递的（只不过object的值是指向heap的地址）。先看一个基本类型的参数值：\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "20 30\n"
     ]
    }
   ],
   "source": [
    "function increase(num) {\n",
    "    num += 10;\n",
    "    return num;\n",
    "}\n",
    "\n",
    "let age = 20;\n",
    "let result = increase(20);\n",
    "console.log(age,result);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果是一个引用类型的值作为函数的参数，情况就有所不同，看下面这个例子："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "90\n"
     ]
    }
   ],
   "source": [
    "function setScore(stu) {\n",
    "    stu.score = 90;\n",
    "}\n",
    "\n",
    "let stu1 = {};\n",
    "setScore(stu1);\n",
    "console.log(stu1.score);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "虽然上面的例子看起来没什么难以理解的地方，但请和下面的例子进行对比："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "90\n"
     ]
    }
   ],
   "source": [
    "function setScore(stu) {\n",
    "    stu.score = 90;\n",
    "    stu = {};\n",
    "    stu.score = 80;\n",
    "}\n",
    "\n",
    "let stu2 = {};\n",
    "setScore(stu2);\n",
    "console.log(stu2.score);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "false\n",
      "true true\n"
     ]
    },
    {
     "ename": "ReferenceError",
     "evalue": "stu is not defined",
     "output_type": "error",
     "traceback": [
      "evalmachine.<anonymous>:6",
      "console.log(stu)",
      "            ^",
      "",
      "ReferenceError: stu is not defined",
      "    at evalmachine.<anonymous>:6:13",
      "    at Script.runInThisContext (vm.js:131:20)",
      "    at Object.runInThisContext (vm.js:297:38)",
      "    at run ([eval]:1054:15)",
      "    at onRunRequest ([eval]:888:18)",
      "    at onMessage ([eval]:848:13)",
      "    at process.emit (events.js:315:20)",
      "    at emit (internal/child_process.js:881:12)",
      "    at processTicksAndRejections (internal/process/task_queues.js:85:21)"
     ]
    }
   ],
   "source": [
    "console.log({} == {} )\n",
    "a = {}\n",
    "b = a\n",
    "console.log(b==a,b===a)\n",
    "\n",
    "console.log(stu)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "从上面的案例中可以看出，函数的实际参数如果是引用类型的值，在函数运行时，形式参数接收到这个引用类型的实际参数值时，也是复制引用地址，而不是直接让形式参数和实际参数完全一致。在函数内部生成的新的对象，也会在函数运行结束后销毁。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义回调函数\n",
    "\n",
    "回调函数(callback)，是一种作为参数的函数，作为参数的函数在调用它的函数执行完毕后执行。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "获取远程数据\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'远程数据'"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "function dosomething(msg,callback) {\n",
    "    console.log(msg)\n",
    "    if(typeof callback === \"function\"){\n",
    "        return callback()\n",
    "    }\n",
    "}\n",
    "\n",
    "dosomething('获取远程数据',function(){\n",
    "    //     执行远程数据调取任务\n",
    "    setTimeout(()=>{},2000)\n",
    "    //     返回数据\n",
    "    return '远程数据'\n",
    "})"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Javascript (Node.js)",
   "language": "javascript",
   "name": "javascript"
  },
  "language_info": {
   "file_extension": ".js",
   "mimetype": "application/javascript",
   "name": "javascript",
   "version": "13.13.0"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
